#TODO: remove
#from __future__ import print_function

import sys
import random
import logging

logger = logging.getLogger("dabble")

WILDCARD = '*'


class Board(object):
    def __init__(self, num_cols, num_rows):
        logger.debug("Creating a new board of %d cols and %d rows." % (num_cols, num_rows))
        self.NUM_COLS = num_cols
        self.NUM_ROWS = num_rows
        self.cells = [[None] * self.NUM_COLS for r in range(0, self.NUM_ROWS)]

    def get_cells(self):
        return self.cells

    def get_cell(self, row, column):
        return self.cells[row][column]

    def set_horizontal_word(self, row, column, word):
        Board.__check_word_length(column, len(word), self.NUM_COLS)
        words, new_word = self.__check_horizontal_word_against_existing_words(row, column, word)

        fixed_word = []
        for index, character in enumerate(word):
            if character != WILDCARD:
                self.cells[row][column + index] = character
                fixed_word.append(character)
            else:
                fixed_word.append(self.cells[row][column + index])

        words.append("".join(fixed_word))
        return words

    def set_vertical_word(self, row, column, word):
        Board.__check_word_length(row, len(word), self.NUM_ROWS)
        words, new_word = self.__check_vertical_word_against_existing_words(row, column, word)

        fixed_word = []
        for index, character in enumerate(word):
            if character != WILDCARD:
                self.cells[row + index][column] = character
                fixed_word.append(character)
            else:
                fixed_word.append(self.cells[row + index][column])

        words.append("".join(fixed_word))
        return words

    def __check_horizontal_word_against_existing_words(self, row, column, word):
        words = []
        for index, character in enumerate(word):
            value = self.cells[row][column + index]

            if value and (value != character and character != WILDCARD):
                raise Exception("The word does not fit.")

            if character != WILDCARD:
                new_word = self.__get_vertical_words(row, column + index, character)
                if new_word:
                    words.append(new_word)

        return (words, new_word)

    def __check_vertical_word_against_existing_words(self, row, column, word):
        words = []
        for index, character in enumerate(word):
            value = self.cells[row + index][column]
            if value and (value != character and character != WILDCARD):
                raise Exception("The word does not fit.")

            if character != WILDCARD:
                new_word = self.__get_horizontal_words(row + index, column, character)
                if new_word:
                    words.append(new_word)

        return (words, new_word)

    def __find_vertical_extreme(self, r, c, direction):
        if (r + direction >= 0 and r + direction < self.NUM_ROWS) and (self.cells[r + direction][c]):
            return self.__find_vertical_extreme(r + direction, c, direction)
        else:
            return r

    def __find_horizontal_extreme(self, r, c, direction):
        if (c + direction >= 0 and c + direction < self.NUM_COLS) and (self.cells[r][c + direction]):
            return self.__find_horizontal_extreme(r, c + direction, direction)
        else:
            return c

    def __get_vertical_words(self, row, column, letter):
        def extract_vertical_word(min, max):
            return "".join(
                [self.cells[i][column]
                    if self.cells[i][column]
                    else letter
                        for i in range(min, max)])

        min_ = self.__find_vertical_extreme(row, column, -1)
        max_ = self.__find_vertical_extreme(row, column, 1) + 1
        if max_ - min_ > 2:
            return extract_vertical_word(min_, max_)
        else:
            return ""

    def __get_horizontal_words(self, row, column, letter):
        def extract_horizontal_word(min, max):
            return "".join(
                [self.cells[row][i]
                    if self.cells[row][i]
                    else letter
                        for i in range(min, max)])

        min_ = self.__find_horizontal_extreme(row, column, -1)
        max_ = self.__find_horizontal_extreme(row, column, 1) + 1
        if max_ - min_ > 2:
            return extract_horizontal_word(min_, max_)
        else:
            return ""

    def get_letters(self):
        letters = []
        for col in range(0, self.NUM_COLS):
            for row in range(0, self.NUM_ROWS):
                letter = self.get_cell(row, col)
                if letter:
                    letters.append({"x": row, "y": col, "letter": letter})
        return letters

    @staticmethod
    def __check_word_length(index, word_length, board_length):
        if word_length < 2:
            raise Exception("The word is too short.")

        if index + word_length > board_length:
            raise Exception("The word is too long.")


class Player(object):
    def __init__(self, uuid, name, mail):
        self.uuid = uuid
        self.name = name
        self.mail = mail

    def __unicode__(self):
        return '<Player %s:%s>' % (self.uuid, self.name)

    def __str__(self):
        return unicode(self).encode('utf-8')

    def __eq__(self, other):
        """
        Comparison function. Will return a positive match if
        the other object has the same name OR uuid is the same.
        """
        if self.name == other.name:
            return True

        if self.uuid == other.uuid:
            return True

        if self.mail == other.mail:
            return True

        return False


class Word(object):
    def __init__(self, x, y, letters, is_horizontal):
        self.x = x
        self.y = y
        self.letters = letters
        self.is_horizontal = is_horizontal

    def __unicode__(self):
        return '<Word %s:horizontal:%s>' % (self.letters, self.is_horizontal)

    def __str__(self):
        return unicode(self).encode('utf-8')


class WordFactory(object):
    @staticmethod
    def create_word(letters):
        # Assume that the letters are sorted for now (bad idea!)
        xmin = min(letters, key=lambda l: l["x"])
        xmax = max(letters, key=lambda l: l["x"])
        is_horizontal = (xmin <= xmax)
        letters_in_word = [l["letter"] for l in letters]
        x = letters[0]["x"]
        y = letters[0]["y"]

        return Word(x, y, "".join(letters_in_word), is_horizontal)


class Game(object):
    """
    A game is a board with 15x15 cells, whereas each cell can either
    hold a letter or not.
    """
    def __init__(self, players, game_uuid, players_letters):
        self.players = players

        self._playerMap = dict()
        self._lettersMap = dict()
        for i, p in enumerate(self.players):
            self._playerMap[p.uuid] = players_letters[i]
            self._lettersMap[p.uuid] = players_letters[i].get_active_letters()

        self.uuid = game_uuid
        self.score = {}
        for p in self.players:
            self.score[p.uuid] = 0
        self.active_player = random.randint(0, 1)
        self.board = Board(15, 15)

    def num_players(self):
        return len(self.players)

    def get_active_player(self):
        return self.players[self.active_player]

    def get_score(self, player):
        return self.score[player.uuid]

    def get_user_letters(self, user_uuid):
        """
            Return the characters given to the user by the game
            >>> game.get_user_randomgameletters("1322-21322fsdsfds-dsfsdf").get_active_letters()
            ...
        """
        val = self._lettersMap.get(user_uuid, None)
        return val

    def get_unused_letters(self, user_uuid):
        """
            Return the user associated RandomGameLetters object.
            >>> game.get_user_randomgameletters("1322-21322fsdsfds-dsfsdf").get_all_letters()
            ...
        """
        val = self.get_user_randomgameletters(user_uuid).get_all_letters()
        return val

    def get_user_randomgameletters(self, user_uuid):
        return self._playerMap.get(user_uuid, None)

    def get_board(self):
        return self.board

    def is_word_valid(self, player, word):
        player_random_game_object = self.get_user_randomgameletters(player.uuid)
        letters = word.letters

        # First check that the letters are valid. The user have a predefined
        # list of valid chars and a count for have many times he may use
        # each of them.
        if not player_random_game_object.is_all_letters_valid(letters):
            return False

        # TODO: Check that the word is a valid Norwegian word!
        return True

    def set_word(self, player, word):
        # TODOs:
        # 2: Verify that the word fits on the board

        # Check that the correct player is playing
        if player.uuid is self.get_active_player():
            logger.warn("%s tried to set word: %s, while it should be player: %s" % (player, word, self.get_active_player()))
            return False

        if not self.is_word_valid(player, word):
            logger.warn("%s is using an invalid word: %s" % (player, word))
            return False

        if word.is_horizontal:
            self.board.set_horizontal_word(word.x, word.y, word.letters)
        else:
            self.board.set_vertical_word(word.x, word.y, word.letters)

        # Compute the word score
        word_score = 1  # TODO: actually compute this..
        self.score[player.uuid] += word_score

        # Determine who's next to play
        self.active_player = (self.active_player + 1) % self.num_players()

        return word_score

    def __unicode__(self):
        return '<Game players:%s active_player:%s>' % (self.players, self.players[self.active_player])
